package lenfs;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import lenfs.impl.MountingImpl;
import lenfs.impl.NFSImpl;
import lenfs.impl.ServerImpl;
import lenfs.util.*;

public class Server {

	private Map<Long, ProgMap> mapProgMap;
	private Map<Integer, Export> mapExport;

	private Map<FileHandle, FileDescriptor> mapHandleDescriptor;
	private Map<FileDescriptor, FileHandle> mapDescriptorHandle;
	private ReadWriteLock lockHandle;
	private ReadWriteLock lockDescriptor;

	public Server() {
		mapProgMap = new HashMap<Long, ProgMap>();
		mapExport = new HashMap<Integer, Export>();
		mapHandleDescriptor = new HashMap<FileHandle, FileDescriptor>();
		mapDescriptorHandle = new HashMap<FileDescriptor, FileHandle>();
		lockHandle = new ReentrantReadWriteLock();
		lockDescriptor = new ReentrantReadWriteLock();
		completeMaps();
	}

	private void completeMaps() {
		addServer(new MountingImpl(this));
		addServer(new NFSImpl(this));
		addExport(new Export("/Users/yudinaa/Pictures"));
	}

	public File getRootFile(File file) {
		List<File> roots = Arrays.asList(File.listRoots());
		File wFile = file;
		while (wFile != null) {
			if (roots.contains(wFile)) {
				return wFile;
			}
			wFile = wFile.getParentFile();
		}
		return null;
	}

	private FileHandle generateFileHandle(final File localFile,
			final int exportHash) {
		FileHandle fileHandle = null;

		File localAbsoluteFile = localFile.getAbsoluteFile();

		lockDescriptor.readLock().lock();
		fileHandle = mapDescriptorHandle.get(localAbsoluteFile);
		lockDescriptor.readLock().unlock();

		if (fileHandle == null) {

			File root = getRootFile(localAbsoluteFile);
			fileHandle = new FileHandle(exportHash, root, localAbsoluteFile);
			FileDescriptor desc = new FileDescriptor(localAbsoluteFile);

			lockHandle.writeLock().lock();
			mapHandleDescriptor.put(fileHandle, desc);
			lockDescriptor.writeLock().lock();
			lockHandle.writeLock().unlock();
			mapDescriptorHandle.put(desc, fileHandle);
			lockDescriptor.writeLock().unlock();
		}

		return fileHandle;
	}

	public FileHandle generateFileHandle(final File f, final Export export) {
		return generateFileHandle(f, export.hashCode());
	}

	public FileHandle generateFileHandle(final File f,
			final FileHandle rootHandle) {
		return generateFileHandle(f, rootHandle.getExport());
	}

	public FileDescriptor getFile(final FileHandle fileHandle) {
		if (fileHandle != null) {
			lockHandle.readLock().lock();
			FileDescriptor resource = mapHandleDescriptor.get(fileHandle);
			lockHandle.readLock().unlock();

			if (resource != null) {
				if (resource.getFile().exists())
					return resource;

				lockHandle.writeLock().lock();
				mapHandleDescriptor.remove(fileHandle);
				lockDescriptor.writeLock().lock();
				lockHandle.writeLock().unlock();
				mapDescriptorHandle.remove(resource);
				lockDescriptor.writeLock().unlock();
			}

		}
		return null;
	}

	public void deleteEntry(final File file) {
		lockDescriptor.readLock().lock();
		FileHandle fileHandle = mapDescriptorHandle.get(file);
		lockDescriptor.readLock().unlock();

		if (fileHandle != null) {
			lockHandle.writeLock().lock();
			mapHandleDescriptor.remove(fileHandle);
			lockDescriptor.writeLock().lock();
			lockHandle.writeLock().unlock();
			mapDescriptorHandle.remove(file);
			lockDescriptor.writeLock().unlock();
		}
	}

	public void rebindEntry(final File newFile, final File oldFile) {
		lockDescriptor.readLock().lock();
		FileHandle fileHandle = mapDescriptorHandle.get(oldFile);
		lockDescriptor.readLock().unlock();

		if (fileHandle != null) {
			FileDescriptor fileDescriptor = new FileDescriptor(newFile);

			lockHandle.writeLock().lock();
			mapHandleDescriptor.put(fileHandle, fileDescriptor);
			lockDescriptor.writeLock().lock();
			lockHandle.writeLock().unlock();
			mapDescriptorHandle.remove(oldFile);
			mapDescriptorHandle.put(fileDescriptor, fileHandle);
			lockDescriptor.writeLock().unlock();
		}
	}

	private void addServer(final ServerImpl server) {
		final long prog = server.getProg();
		ProgMap progMap = mapProgMap.get(prog);
		if (progMap == null) {
			progMap = new ProgMap();
			mapProgMap.put(prog, progMap);
		}
		progMap.newEntry(server);
	}

	private void addExport(final Export export) {
		mapExport.put(export.hashCode(), export);
	}

	public ProgMap getProg(final long prog) {
		return mapProgMap.get(prog);
	}

	public Export getExport(final long export) {
		return mapExport.get(export);
	}

	public Export getExport(final String filePath, final XDR xdrPacket) {
		List<Export> exportsArrayList = new ArrayList<Export>(
				mapExport.values());
		for (Export export : exportsArrayList) {
			if (export.packetFromPath(filePath, xdrPacket))
				return export;
		}
		return null;
	}
}
